package abacab.remote;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import abacab.remote.message.Message;
import abacab.remote.protocol.MessageProtocol;

/**
 * Represents a connection
 */
public abstract class Connection {
	/** The logger */
	private static final Logger				logger	= LoggerFactory.getLogger(Connection.class);

	/** The registry */
	private final ConnectionRegistry	registry;

	/**
	 * Create the connection
	 * 
	 * @param registry
	 *          the registry
	 */
	protected Connection(ConnectionRegistry registry) {
		this.registry = registry;
	}

	/**
	 * @return the remote host name & port for the connection formated as "hostname:port"
	 */
	public abstract String getRemoteHostPort();

	/**
	 * Get the remote address
	 * 
	 * @return the remote address
	 */
	public abstract InetSocketAddress getRemoteAddress();

	/**
	 * Close the connection
	 */
	public abstract void close();

	/**
	 * Register the connection
	 */
	protected final void register() {
		try {
			Connection oldCnx = this.registry.registerConnection(getRemoteAddress(), this);
			if (oldCnx != null) {
				logger.warn("A connection was not unregistered: {}", oldCnx);
			}
		}
		catch (IllegalAccessException e) {
			close();
		}
	}

	/**
	 * Unregister the connection
	 */
	protected final void unregister() {
		this.registry.unregisterConnection(getRemoteAddress(), this);
	}

	/**
	 * Send a message
	 * 
	 * @param message
	 *          a message
	 * @throws IOException
	 *           if an error occurred while sending
	 */
	public final void send(Message message) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		MessageProtocol.encode(message, baos, this.registry.getSerilizationExt());
		send(baos.toByteArray());
	}

	/**
	 * Send the message as bytes
	 * 
	 * @param message
	 *          the message
	 */
	protected abstract void send(byte[] message);

	/**
	 * Notifies that a message was received
	 * 
	 * @param message
	 *          the message
	 */
	public final void messageReceived(byte[] message) {
		ByteArrayInputStream bais = new ByteArrayInputStream(message);
		try {
			Message msg = MessageProtocol.decode(bais, this.registry.getSerilizationExt(), this);
			this.registry.messageReceived(this, msg);
		}
		catch (IOException e) {
			// Shouldn't happen with a byte array input stream
			logger.error("A message could not be decoded", e);
		}
		catch (ClassNotFoundException e) {
			logger.error("A message could not be decoded", e);
			// TODO: tell the remote system that the message could not be treated
		}
	}
}